home *** CD-ROM | disk | FTP | other *** search
Text File | 1994-11-30 | 10.5 KB | 352 lines | [TEXT/KAHL] |
- /*
- *——— Lefty.cp ——————————————————————————————————————————————————————————
- * Copyright © Paul D. Ferguson, 1990-94. All rights reserved.
- *
- * This source code is a complete THINK Class Library application for
- * use with Apple's MIDI Manager.
- *
- * This program is freeware, although I retain all rights to it. You
- * are free to distribute it provided you don't sell it.
- *
- * If you have any comments about this program, I can be reached on
- * CompuServe at 70441,3055.
- *
- *
- * Description:
- * This program has no user interface to speak of. It's main purpose is
- * to illustrate the use of the CMIDI objects. These objects are
- * described in the CMIDI Programmers Guide.
- *
- * Like a left-handed guitar, Lefty makes your MIDI keyboard into a
- * “left-handed keyboard”.
- *
- * It reverses the note numbers of Note On, Note Off, and Aftertouch
- * MIDI messages, i.e. the low notes are towards the right end of the
- * keyboard, while the high notes are towards the left. All other
- * MIDI messages are simply passed straight through without change.
- *
- * The note values are also calculated so that the black keys are
- * reversed correctly to complete the illusion. Thus when filtering
- * through Lefty, middle C is found on the E key. The graphic
- * in the “About Lefty” box illustrates this.
- *
- * Usage:
- * ——————
- * You must have Apple's MIDI Manager (version 1.2 or later) software
- * to use this program. In the simplest configuration, just route the
- * MIDI Manager output port through Lefty, and back into the input port.
- *
- * You can also play back songs from a sequencer through Lefty for some
- * pretty unusual effects.
- *
- * Implementation Notes:
- * —————————————————————
- * This source code is for use Symantec C++ 6.0 and the THINK Class Library.
- * Since this program is so simple, I have reduced the application to
- * this single source file. Although Lefty doesn’t use most of TCL's
- * features, you still need to include them in the project. As a
- * result, the application is about 110K+ in size which is “kind of large”.
- * Oh well, memory is cheap...
- *
- * If you really wish to use this program, and memory is an issue, you
- * should be able to rip out all the TCL files. All you need is to
- * handle the menu events.
- *—————————————————————————————————————————————————————————————————————
- */
-
- #include <CApplication.h>
- #include <Commands.h>
- #include <CBartender.h>
- #include <CDesktop.h>
- #include <CError.h>
-
- #include "CMIDIClient.h"
- #include "CMIDIInputPort.h"
- #include "CMIDIOutputPort.h"
- #include "CMIDITimePort.h"
-
- #define PORTNAMES 1024 // Resource ID for strings
- #define ABOUTBOX 1024 // An ALRT
-
- #define FLIPNOTE 62 // Point at which to flip note numbers
-
- #define INBUFSIZE 30000 // Nice big buffer
-
- #define menuLEFTY 128 // MENU ID for DoCommand()
- #define cmdEnable 12801 // Only two commands needed
- #define cmdDisable 12802
-
- #define TIMEPORT 128
- #define INPORT 129
- #define OUTPORT 130
-
- class CLeftyApp : public CApplication // This is Lefty...
- {
-
- public:
-
- CMIDIInputPort * itsMIDIIn;
- CMIDIOutputPort * itsMIDIOut;
- CMIDITimePort * itsMIDITime;
-
- void ILeftyApp(void);
- void UpdateMenus(void); // Override a couple methods
- void DoCommand(long theCommand);
- void Exit(void);
-
- private:
-
- static pascal short LeftyReadHook(MIDIPacket * ThePacketPtr, long TheRefCon);
- static pascal short NormalReadHook(MIDIPacket * ThePacketPtr, long TheRefCon);
- static pascal void myConnectProc(short refnum, long refcon, short portType,
- OSType clientID, OSType portID,
- Boolean connect, short direction);
-
- static int outRefNum;
- static unsigned char newNote[128]; // Note translation table
-
- };
-
- // Referenced externals
-
- extern OSType gSignature;
- extern CMIDIClient * gMIDIClient;
- extern CApplication * gApplication;
- extern CBartender * gBartender;
- extern CError * gError;
-
- /*
- * These variables are used locally. Because we're running
- * in interrupt context for the MIDI handler, we don't want
- * to go through the overhead of putting these into objects,
- * only to have to extract them for the handler.
- */
-
- unsigned char CLeftyApp::newNote[128];
- int CLeftyApp::outRefNum; // For direct MIDI Manager Writes…
-
- /*
- *——— main ——————————————————————————————————————————————————————
- */
- void main()
- {
- gApplication = new(CLeftyApp);
- ((CLeftyApp *)gApplication)->ILeftyApp();
- gApplication->Run();
- gApplication->Exit();
- }
-
- /*
- *——— LeftyReadHook ———————————————————————————————————————————————
- * This is our application's MIDI Manager read function. It
- * normally operates in interrupt context.
- *
- * This function looks to see whether the packet is a Note On,
- * Note Off, or Poly After Pressure. If so, it translates the
- * key number. All other messages are passed straight through.
- *
- * NOTE: This read hook does not properly handle MIDI messages
- * which contain two or more Note On/Off messages (e.g.
- * 80 66 7F 70 7F 75 7F ...). If you need this, then you'll
- * just have to add it yourself.
- *——————————————————————————————————————————————————————————————————
- */
- pascal short CLeftyApp::LeftyReadHook(MIDIPacket * ThePacketPtr, long TheRefCon)
- {
- long SysA5 = SetA5(TheRefCon);
- register unsigned char theFlags;
- register unsigned int it;
-
- theFlags = ThePacketPtr->flags;
- if (theFlags & midiMgrType) // Ignore error packets.
- {
- SetA5(SysA5);
- return(midiMorePacket); // Throw away
- }
-
- // Do we want to alter this packet?
-
- if ( (ThePacketPtr->len == 9) // six byte header & three MIDI bytes
- && (ThePacketPtr->data[0] >= 0x80) // is Note On, Note Off, or Key Pressure
- && (ThePacketPtr->data[0] <= 0xAF)
- && ((theFlags & midiContMask) == midiNoCont) ) // Is a simple packet
- {
- it = ThePacketPtr->data[1]; // translate the note byte
- ThePacketPtr->data[1] = CLeftyApp::newNote[it]; // using a lookup table
- }
- // In many cases, it’s just as easy to call the MIDI Manager directly,
- // as in this case of echoing each packet back out, assuming you
- // already know that the MIDI Manager is present and functioning.
- //
- // Presumably, because this code wouldn’t get executed at all unless
- // that were the case, we can make this assumption.
-
- MIDIWritePacket(CLeftyApp::outRefNum, ThePacketPtr);
- SetA5(SysA5);
- return (midiMorePacket); // Get next packet
- }
-
- /*
- *——— NormalReadHook ———————————————————————————————————————————————
- * This is the “Disabled” readHook. It simply passes every packet
- * straight through.
- *——————————————————————————————————————————————————————————————————
- */
- pascal short CLeftyApp::NormalReadHook(MIDIPacket * ThePacketPtr, long TheRefCon)
- {
- long SysA5 = SetA5(TheRefCon);
-
- if (! (ThePacketPtr->flags & midiMgrType) ) // Ignore error packets.
- {
- MIDIWritePacket(CLeftyApp::outRefNum, ThePacketPtr);
- }
- SetA5(SysA5);
- return (midiMorePacket); // Get next packet
- }
-
-
-
- pascal void CLeftyApp::myConnectProc(short refnum, long refcon, short portType,
- OSType clientID, OSType portID,
- Boolean connect, short direction)
- {
- portType &= midiPortTypeMask;
- if (portType == midiPortTypeTime)
- {
- if (direction == midiInternalSync)
- {
- // There's stuff to be done here...
- }
- else
- {
- // but I ain't doin' it...
- }
- }
- }
-
- /*
- *——— ILeftyApp —————————————————————————————————————————————————
- * Initialize the application. Most of the work here is in
- * creating and initializing the CMIDI objects.
- *———————————————————————————————————————————————————————————————
- */
- void CLeftyApp::ILeftyApp(void)
- {
- register int i;
- Str255 theString;
- OSErr theResult;
-
- gSignature = 'Left';
-
- CApplication::IApplication(4, 20480L, 2048L, 2048L);
- DoCommand(cmdAbout);
-
- gBartender->SetDimOption(menuLEFTY, dimNONE);
- gBartender->SetUnchecking(menuLEFTY, TRUE);
-
- // Set up note mapping table. By using a table, this program
- // can be generalized for any kind of mappings. Simply set the
- // netNote[] array to the appropriate values.
-
- for (i = 0; i < 128; ++i)
- CLeftyApp::newNote[i] = 0; // just being sure
-
- for (i = 0; i < (2*FLIPNOTE) ; ++i)
- CLeftyApp::newNote[i] = (2*FLIPNOTE) - i;
-
- // Sign into MIDI Manager. Open input and output ports.
- // Also open a time port, although we don't use it.
-
- gMIDIClient = new(CMIDIClient);
- theResult = gMIDIClient->IMIDIClient(128);
- if (! gError->CheckOSError(theResult)) // Can't go on...
- DoCommand(cmdQuit);
-
- // Time base
- GetIndString(theString, PORTNAMES, 3);
- itsMIDITime = new(CMIDITimePort); // We don't actually use this time port
- itsMIDITime->IMIDITimePort(theString, 'ATim', TRUE, midiFormatMSec);
- itsMIDITime->LoadPatches('Port', TIMEPORT);
- itsMIDITime->SetConnection((ProcPtr) CLeftyApp::myConnectProc);
-
- // Output port
- GetIndString(theString, PORTNAMES, 2);
- itsMIDIOut = new(CMIDIOutputPort);
- itsMIDIOut->IMIDIOutputPort(theString, 'Out ',
- TRUE, itsMIDITime, midiGetCurrent);
- itsMIDIOut->LoadPatches('Port', OUTPORT); // Did we have any patches?
- CLeftyApp::outRefNum = itsMIDIOut->GetRefNum(); // Save for easy reference in our read hook
-
- // Input port
- GetIndString(theString, PORTNAMES, 1);
- itsMIDIIn = new(CMIDIInputPort);
- itsMIDIIn->IMIDIInputPort(theString, 'In ',
- TRUE, itsMIDITime, midiGetEverything, INBUFSIZE, (ProcPtr) LeftyReadHook);
- itsMIDIIn->LoadPatches('Port', INPORT); // Did we have any patches?
-
- return;
- }
-
- /*
- *——— UpdateMenus —————————————————————————————————————————
- */
- void CLeftyApp::UpdateMenus(void)
- {
- register ProcPtr theProc;
-
- inherited::UpdateMenus();
-
- gBartender->EnableCmd(cmdEnable);
- gBartender->EnableCmd(cmdDisable);
-
- theProc = itsMIDIIn->GetReadHook();
-
- gBartender->CheckMarkCmd(cmdEnable, (theProc == (ProcPtr) LeftyReadHook));
- gBartender->CheckMarkCmd(cmdDisable, (theProc == (ProcPtr) NormalReadHook));
- }
-
- /*
- *——— DoCommand ———————————————————————————————————————————
- */
- void CLeftyApp::DoCommand(long theCommand)
- {
- switch (theCommand)
- {
- case cmdEnable:
- itsMIDIIn->SetReadHook((ProcPtr) LeftyReadHook);
- break;
- case cmdDisable:
- itsMIDIIn->SetReadHook((ProcPtr) NormalReadHook);
- break;
- case cmdAbout:
- PositionDialog('ALRT', ABOUTBOX);
- Alert(ABOUTBOX, FALSE);
- break;
- default:
- inherited::DoCommand(theCommand);
- break;
- }
- }
-
- /*
- *——— Exit ———————————————————————————————————————————————————————
- * Log out of the MIDI Manager, and quit.
- *————————————————————————————————————————————————————————————————
- */
- void CLeftyApp::Exit()
- {
- itsMIDIIn->SavePatches('Port', INPORT);
- itsMIDIOut->SavePatches('Port', OUTPORT);
- itsMIDITime->SavePatches('Port', TIMEPORT);
-
- itsMIDIIn->Dispose();
- itsMIDIOut->Dispose();
- itsMIDITime->Dispose();
-
- gMIDIClient->Dispose(); // Sign out of MIDI Manager
-
- ExitToShell();
- }
-
- // end of Lefty.cp
-